package com.wnxy.sk.product.service.impl;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wnxy.sk.common.exception.Assert;
import com.wnxy.sk.common.result.Result;
import com.wnxy.sk.common.result.enums.impl.BusinessCode;
import com.wnxy.sk.product.api.OrderApi;
import com.wnxy.sk.product.api.dto.OrderDto;
import com.wnxy.sk.product.api.dto.OrderVo;
import com.wnxy.sk.product.config.OrderProducer;
import com.wnxy.sk.product.constant.OrderConstant;
import com.wnxy.sk.product.constant.PromotionStatus;
import com.wnxy.sk.product.constant.RedisConstant;
import com.wnxy.sk.product.dto.OrderPayDto;
import com.wnxy.sk.product.entity.Product;
import com.wnxy.sk.product.entity.PromotionSeckill;
import com.wnxy.sk.product.mapper.ProductMapper;
import com.wnxy.sk.product.mapper.PromotionSeckillMapper;
import com.wnxy.sk.product.service.IPromotionSeckillService;
import com.wnxy.sk.product.vo.PaymentVo;
import com.wnxy.sk.product.vo.PromotionOrderVo;
import com.wnxy.sk.product.vo.PromotionProductVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.util.Asserts;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.List;

/**
 * <p>
 * 秒杀活动表 服务实现类
 * </p>
 *
 * @author Jet
 * @since 2022-10-11
 */
@Service
@Slf4j
public class PromotionSeckillServiceImpl extends ServiceImpl<PromotionSeckillMapper, PromotionSeckill> implements IPromotionSeckillService {

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private OrderApi orderApi;
    @Autowired
    private ProductMapper productMapper;
    @Autowired
    private OrderProducer orderProducer;

    /**
     * 分析？
     * 1、当有10万个请求同时访问秒杀活动列表，且第一次访问，此时缓存中没有数据；
     * 2、导致这10万个并发请求同时访问数据库，数据库扛不住。
     * 3、这种现象叫做“缓存击穿”。 缓存中没有数据，数据库中有数据。
     * 解决？
     * 1、提前查询秒杀活动列表，放入缓存。
     * 2、这种就叫做数据预热。
     * 步骤：
     * 1、不使用@Cacheable注解；
     * 2、通过定时任务，查询秒杀时间已到，活动未开始的秒杀活动，放入Redis缓存
     * 3、最后，再在秒杀活动列表查询中，从Redis中获取数据
     * 技术：
     * 1、RedisTemplate 操作Redis
     * 2、需要重写key、value序列化方式为序列化为json
     */
    //@Cacheable(value = "promotion:seckill",key = "'list'")
    @Override
    public List<PromotionProductVo> findPromotionSeckill() {
        // 从缓存中获取数据
        List<PromotionProductVo> voList =
                redisTemplate.opsForList().range(RedisConstant.PRODUCT_SECKILL,0,-1);
        // 如果没有获取到，查询数据库，最后再放入缓存
        if (CollectionUtils.isEmpty(voList)) {
            voList = baseMapper.findPromotionSeckill(PromotionStatus.START);
            if (voList != null && voList.size() > 0) {
                redisTemplate.opsForList().leftPushAll(RedisConstant.PRODUCT_SECKILL, voList);
            }
        }
        return voList;
    }
    @Override
    public List<PromotionProductVo> findPromotionSeckill(int status) {
        // 从缓存中获取数据
        List<PromotionProductVo> voList = baseMapper.findPromotionSeckill(status);
        return voList;
    }

    @Override
    public void updateStatusByIds(List<Integer> promotionIds) {
        // UPDATE pms_promotion_seckill SET STATUS=1 WHERE id IN (..)
        Wrapper<PromotionSeckill> wrapper = Wrappers.lambdaUpdate(PromotionSeckill.class)
                .set(PromotionSeckill::getStatus, PromotionStatus.START)
                .in(PromotionSeckill::getId, promotionIds);
        this.update(wrapper);
    }

    @Override
    public PromotionOrderVo submitOrder(String userId, Integer promotionId) {

        //1、秒杀活动状态：0-未开始 1-进行中  2-已结束； 先进行状态判断
        PromotionSeckill ps = this.getById(promotionId);

        //1.1 秒杀活动不存在
        Assert.error(ps, BusinessCode.SECKILL_NOT_EXISTS);

        //1.1 秒杀活动状态判断
        Assert.error(!(ps.getStatus() == PromotionStatus.UN_START),BusinessCode.SECKILL_NOT_START);
        Assert.error(!(ps.getStatus() == PromotionStatus.FINISH),BusinessCode.SECKILL_ALREADY_END);

        //2. 判断：一个秒杀活动一个用户只能参与一次
        Boolean flag = redisTemplate.opsForSet()
                        .isMember(RedisConstant.SECKILL_USER+userId,promotionId);
        Assert.error(!flag,BusinessCode.SECKILL_REPEATED_BUY);

        //3. 扣减Redis库存（支持高并发场景的库存设计）
        Object obj = redisTemplate.opsForList().rightPop(RedisConstant.SECKILL_COUNT + promotionId);
        Assert.error(obj,BusinessCode.SECKILL_PRODUCT_SOLD_OUT);

        //TODO 4. 生成订单，通过OpenFeign远程调用订单微服务
        // 服务提供者：订单微服务 sk-order-service
        // 服务调用者：产品微服务
        Product product = productMapper.selectById(ps.getProductId());
        OrderDto orderDto = new OrderDto();
        orderDto.setUserId(userId);
        orderDto.setProductId(ps.getProductId());
        orderDto.setProductPic(product.getPic());
        orderDto.setProductName(product.getTitle());
        orderDto.setPrice(product.getPrice());
        orderDto.setPromotionPrice(product.getPromotionPrice());
        // 远程调用订单微服务生成订单
        Result result = orderApi.createOrder(orderDto);

        //5. 生成订单后，记录当前用户购买的秒杀商品到redis的set集合
        redisTemplate.opsForSet().add(RedisConstant.SECKILL_USER+userId,promotionId);

        return PromotionOrderVo.builder()
                .orderSn(result.getData().toString())
                .userId(userId)
                .build();
    }

    @Override
    public PaymentVo confirmOrder(OrderPayDto orderPayDto, String userId) {
        //根据订单编号查询
        Result<OrderVo> result  =
                orderApi.getOrderBySn(orderPayDto.getOrderSn());
        log.warn("Openfegin返回结果：{}",result);
        OrderVo vo = result.getData();

        //订单状态：0->待付款；1->待发货；2->已发货；3->已完成；4->已关闭；5->无效订单
        if(vo.getStatus().equals(OrderConstant.INVALID)){//5
            Assert.error("订单已失效");
        }
        if(vo.getStatus().equals(OrderConstant.CLOSED)){//4
            Assert.error("订单已经关闭");
        }
        if(vo.getStatus().equals(OrderConstant.COMPLETED)){//3
            Assert.error("订单已经完成");
        }
        if(vo.getStatus().equals(OrderConstant.SHIPPED)){//2
            Assert.error("订单已经发货");
        }
        if(vo.getStatus().equals(OrderConstant.PENDING_SHIPMENT)){//1
            Assert.error("订单待发货");
        }

        //查询商品ID
        LambdaQueryWrapper<PromotionSeckill> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.select(PromotionSeckill::getProductId);
        queryWrapper.eq(PromotionSeckill::getId,orderPayDto.getPromotionId());
        PromotionSeckill ps = baseMapper.selectOne(queryWrapper);
        orderPayDto.setProductId(ps.getProductId());//商品Id，如果订单失效需要还原redis库存

        //发送消息到MQ
        orderProducer.sendOrder(orderPayDto);

        return PaymentVo.builder()
                .orderSn(orderPayDto.getOrderSn())
                .promotionId(orderPayDto.getPromotionId())
                .totalAmount(result.getData().getTotalAmount())
                .userId(userId)
                .build();
    }

    @Override
    public Integer payOrder(OrderPayDto orderDto) {
        //更新订单状态，调用订单微服务
        Result<Integer> result = orderApi.update(orderDto.getOrderSn());
        if(result.getCode() == 500){
            Assert.error(result.getMessage());
        }
        //扣减库存
        int count = baseMapper.updateStore(orderDto.getPromotionId());
        if(count<=0){
            Assert.error("库存扣减失败");
        }
        return result.getData();
    }
}















